<?php
/**
 * @package		简易CMS
 * @subpackage	数据库模型管理模块
 * @version		$Id: class_dbmodel.php 23 2012-06-11 18:04:03Z htmambo@gmail.com $
 * @author		hoping
 * @copyright	Copyright (C) 2010 Hoping Software Studio.
 */

//保证此文件不被直接执行
defined( 'IN_JYCMS' ) or die( 'Direct Access to this location is not allowed.' );


class cmsDBModel {
	/** @var 表名 */
	var $_tbl 		= '';
	/** @var 主字段 */
	var $_tbl_key 	= '';
	/** @var 错误信息 */
	var $_error 	= '';
	/**
	 * 系统数据库链接
	 *
	 * @var cmsDB
	 */
	var $_db 		= null;
	/**
	 * @var 字段列表
	 */
	var $_schema		= null;
	/**
	 *
	 * @var 关联定义
	 */
	var $_relation		= array();
	var $_ordergroup = null;
	/**
	* @var 必填项
	*/
	var $_required = array();
	/**
	 *
	 * @var 是否自动重新排序，在指排序时如果重新排序前两位的话容易被忽略，所以，批量时最后再执行重新排序动作
	 */
	var $_reorder = true;

	/**
	* 多表关联的定义
	*
	* @var array
	*/
	var $_join = array();
	/**
	*	定义
	*
	*	@param 字符串 $table 表名
	*	@param 字符串 $key 关键字
	*/
	function cmsDBTable( $table, $key ) {
		global $db;
		$this->_tbl = $table;
		$this->_tbl_key = $key;
		$this->_db = &$db;
	}

	/**
	 * Returns an array of public properties
	 * @return array
	 */
	function getPublicProperties() {
		static $cache = null;
		if (is_null( $cache )) {
			$cache = array();
			foreach (get_class_vars( get_class( $this ) ) as $key=>$val) {
				if (substr( $key, 0, 1 ) != '_') {
					$cache[] = $key;
				}
			}
		}
		return $cache;
	}

	/**
	 * 设置字段列表，如未指定指读本类所对应的表的字段
	 * @private
	 * @since 1.0.6
	 */
	function _setSchema( $array=null ) {
		if (is_array( $array )) {
			$this->_schema = $array;
		} else {
			if(!$this->_tbl){
				exit("Not set table name!");
			}
			$tableFields = $this->_db->getTableFields( array($this->_tbl) );
			$this->_schema = $tableFields[$this->_tbl];
		}
	}

	/**
	 * 获取当前的字段列表
	 * @private
	 * @since 1.0.6
	 */
	function _getSchema() {
		if ($this->_schema == null) {
			$this->_setSchema();
		}
		return $this->_schema;
	}

	/**
	 * Returns the [database] type of the field
	 * @param string The name of the field
	 * @return string The field type
	 * @private
	 * @since 1.0.6
	 */
	function _getFieldType( $name ) {
		$schema = $this->_getSchema();
		if (isset( $schema[$name] )) {
			$result = $schema[$name];
		} else {
			$result = 'text';
		}
		return $result;
	}

	/**
	 *	@return string Returns the error message
	 */
	function getError() {
		return $this->_error;
	}

	/**
	* Gets the value of the class variable
	* @param string The name of the class variable
	* @return mixed The value of the class var (or null if no var of that name exists)
	*/
	function get( $_property ) {
		if(isset( $this->$_property )) {
			return $this->$_property;
		} else {
			return null;
		}
	}

	/**
	* Set the value of the class variable
	* @param string The name of the class variable
	* @param mixed The value to assign to the variable
	*/
	function set( $_property, $_value ) {
		$this->$_property = $_value;
	}

	/**
	 * Resets public properties
	 * @param mixed The value to set all properties to, default is null
	 */
	function reset( $value=null ) {
		$keys = $this->getPublicProperties();
		foreach ($keys as $k) {
			$this->$k = $value;
		}
	}

	/**
	*	将数组赋值给记录
	*
	*	@param array $hash named array
	*	@return null|string	null is operation was satisfactory, otherwise returns an error
	*/
	function bind( $array ) {
		if (!is_array( $array )) {
			$this->_error = strtolower(get_class( $this ))."::bind failed.";
			return false;
		} else {
			$keys = $this->getPublicProperties();
			foreach ($keys as $k) {
				if(isset($array[$k])){
					if(is_array($array[$k])) $array[$k] = ','.implode(',', $array[$k]).',';
					$this->$k = addcslashes($array[$k], '\'\\');//str_replace('\\', '\\\\', $array[$k]);
				}
			}
		}
	}
	private function _checkJoin($k, $v){
		if(!isset($v['columns']) || !$v['columns']){
			$v['columns'] = $k.'.*';
		}
		$a = array('type', 'on', 'table');
		foreach((array)$a as $var){
			if(!isset($v[$var])){
				$this->_error = '关联表的设置有误，请检查！';
				return false;
			}
		}
		$regex = '@a\.([a-zA-Z0-9\-\_]+)@i';
		$result = array();
		preg_match_all($regex, $v['on'], $result);
		if(!$result) {
			$this->_error = '与'.str_replace(array('#__', $this->_db->tablepre), '', $v['table']).'关联查询条件有误';
			return false;
		}
		$keys = $this->getPublicProperties();
		$msg = '';
		foreach((array)$result[1] as $var){
			if(!in_array($var, $keys)){
				$msg .= '关联字段'.$var.'不存在！<br>';
			}
		}
		if($msg) {
			$this->_error = $msg;
			return false;
		}
		return $v;
	}
	private function _buildSQL(){
		$sql = "SELECT a.*";
		$from = "\nFROM `$this->_tbl` AS a\n";
		if($this->_join){
			foreach((array)$this->_join as $k=>$v){
				$v = $this->_checkJoin($k, $v);
				//检查一下关联的表达式，如果没有问题的话，就关联查询
				if($v){
					$sql .= ', '.$v['columns'];
					$from .= "\n" . $v['type'].' `'.$v['table'].'` AS '.$k.' ON '.$v['on']."\n";
				}
			}
		}
		$sql .= $from;
		return $sql;
	}
	/**
	*	加载记录
	*	@param int $oid optional argument, if not specifed then the value of current key is used
	*	@return any result from the database operation
	*/
	function load( $oid=null, $k = '' ) {
		global $control;
		if(!$k) $k = $this->_tbl_key;
		if ($oid === null) {
			return false;
		}
		$sql = $this->_buildSQL();
		if(!$sql) {
			trigger_error($this->_error);
			return false;
		}
		$sql .= "\n WHERE a.`$this->_tbl_key` = '$oid'";
		DB::Query( $sql );
		if($row = DB::fetch_array( )){
			if(!$this->$k){
				foreach ($row as $k=>$v){
					$this->$k = $v;
				}
			}
		}
		$keys = $this->getPublicProperties();
		//处理多附件
		if(in_array('hash', $keys) && $row){
			if(!$row['hash']){
				$row['hash'] = $this->hash = substr(md5($control->time.random(6)), 0,16);
				$this->bind($row);
				$_tmp = $this->_reorder;
				$this->_reorder = false;
				$this->save();
				$this->_reorder = $_tmp;
			}
			$model = new attachments();
			$attachs = $model->loadAll("hash='".$row['hash']."'");
			if($attachs) $row['attachments'] = $attachs;
			else $row['attachments'] = array();
		}
		return $row;
	}

	function loadAll($where = '', $order = '', $k = ''){
		global $control;
		if(!$k) $k = $this->_tbl_key;
		$sql = '';
		$where = $where?' WHERE '.$where:'';
		$order = $order?' ORDER BY '.$order:'';
		if(!$sql = $this->_buildSQL()) return false;
		$sql .= " $where $order";
		if(!$this->_db) $this->_db = $control->db;
		return $this->_db->fetch_all($sql, $k);
	}

	/**
	 * 检测数据是否合法，由各表例程自定义
	 *
	 * @return 判断结果
	 */
	function check(){
		/**
		* 几个常规字段
		*/
		$keys = $this->getPublicProperties();
		foreach((array)$keys as $k){
			if(is_array($this->$k))	$this->$k = serialize ($this->$k);
		}
		if(in_array('intro', $keys)&&in_array('content', $keys)){
			if(!$this->intro && $this->content){
				$this->intro = substr(strip_tags($this->content),0,200);
			}
		}
		if(in_array('lastupdate', $keys)){
			$this->lastupdate = time();
		}
		if(in_array('dateline', $keys) && !$this->get('dateline')){
			$this->dateline = time();
		}
		if($this->_required){
			//检查一些必填字段
			foreach((array)$this->_required as $v){
				if(strlen($this->$v)==0){
					$this->_error = "必填项 $v 没有输入内容！";
					return false;
				}
			}
		}
		return true;
	}

	/**
	* 保存记录（如果key为0时添加，不为0时更新
	*
	* @param boolean If false, null object variables are not updated
	* @return null|string null if successful otherwise returns and error message
	*/
	function save( $updateNulls=false ) {
		$k = $this->_tbl_key;
		$ret = $this->check();
		if(!$ret){
			return false;
		}
		if ($this->$k) {
			$ret = $this->updateObject( $this->_tbl, $this, $this->_tbl_key, $updateNulls );
		} else {
			unset($this->$k);
			$ret = $this->insertObject( $this->_tbl, $this, $this->_tbl_key );
			//获取一下添加后的主键ID值
			$this->$k = DB::insert_id();
		}
		if( !$ret ) {
			$this->_error = strtolower(get_class( $this ))."::save failed";
			return false;
		} else {
			if($this->_reorder){
				$keys = $this->getPublicProperties();
				if(in_array('displayorder', $keys)){
					$w = '';
					if($this->_ordergroup){
						$o = $this->_ordergroup;
						$w = "`".$o."` = '".$this->$o."'";
					}
					$this->reorder( $w );
				}
			}
			return true;
		}
	}

	/**
	*	删除操作
	*
	*	@return true if successful otherwise returns and error message
	*/
	function delete( $oid=null ) {
		$k = $this->_tbl_key;
		if ($oid) {
			$this->$k = intval( $oid );
		}

		$query = "DELETE FROM $this->_tbl"
		. "\n WHERE $this->_tbl_key = " . $this->_db->Quote( $this->$k )
		;
		DB::Query( $query );
		return true;
	}

	/**
	 * 获取数据列表
	 */
	function getitems( $where = '', $sqladd = ''){
		global $control;
		$start = $perpage = $total = 0;

		$page = intval(getgpc('page'));
		if (!$page  || $page<1) $page = 1;
		$total = intval(getgpc("total"));
		if (!$total){
			$sql = "select count(*) from ".$this->_tbl." ".$where;
			$total = $this->_db->result_first($sql);
		}
		if(!$total) return false;
		list($start, $totalpage, $perpage) = $control->page_get_start($page, $total);
		DB::query("SELECT * FROM ".$this->_tbl." $where $sqladd LIMIT $start, ". $perpage);
		$rows = DB::fetch_all('', $this->_tbl_key);
		return array($rows, $page, $start, $totalpage, $perpage, $total);
	}

	/**
	 * 获取符合条件的记录总数
	 *
	 * @param 查询条件 $sqladd
	 * return 记录总数
	 */
	function get_total_num($sqladd = '') {
		$data = DB::result_first("SELECT COUNT(*) FROM ".$this->_tbl." $sqladd");
		return $data;
	}

	/**
	 * 获取需要展示的条目
	 *
	 * @param 当前页码 $page
	 * @param 总条目数 $totalnum
	 * @param 查询条件 $sqladd
	 * return 记录条目列表
	 */
	function get_list($page = 0, $totalnum = 0, $sqladd = '') {
		global $control;
		if(!$totalnum) $totalnum = $this->get_total_num($sqladd);
		list($start, $totalpage, $perpage) = $control->page_get_start($page, $totalnum);
		$order = ' ORDER BY ';
		$keys = $this->getPublicProperties();
		if($this->_ordergroup) $order .= $this->_ordergroup.', ';
		if(in_array('displayorder', $keys)){
			$order .= 'displayorder,';
		}
		$order .= $this->_tbl_key.' DESC';
		DB::query("SELECT * FROM ".$this->_tbl." $sqladd $order LIMIT $start, $perpage");
		$data = DB::fetch_all();
		return $data;
	}

	function insertObject( $table, &$object, $keyName = NULL, $verbose=false ) {
		$fmtsql = "INSERT INTO $table ( %s ) VALUES ( %s ) ";
		$fields = array();
		$fieldlist = get_object_vars( $object );
		foreach ( $fieldlist as $k => $v) {
			if (is_array($v) or is_object($v) or $v === NULL) {
				continue;
			}
			if ($k[0] == '_') { // internal field
				continue;
			}
			$fields[] = $this->_db->NameQuote( $k );
			$values[] = $this->_db->_quoteField( $v, $object->_getFieldType( $k ) );
		}
		if (!DB::Query(sprintf( $fmtsql, implode( ",", $fields ) ,  implode( ",", $values ) ))) {
			return false;
		}
		$id = DB::insert_id();
		if ($keyName && $id) {
			$object->$keyName = $id;
		}
		return true;
	}
	function updateObject( $table, &$object, $keyName, $updateNulls=true ) {
		$fmtsql = "UPDATE $table SET %s WHERE %s";
		$tmp = array();
		$fieldlist = get_object_vars( $object );
		foreach ($fieldlist as $k => $v) {
			if( is_array($v) or is_object($v) or $k[0] == '_' ) { // internal or NA field
				continue;
			}
			if ($k == $keyName) { // PK not to be updated
				$where = $this->_db->NameQuote($k) . '=' . $this->_db->_quoteField( $v, $object->_getFieldType( $k ) );
				continue;
			}
			if ($v === NULL && !$updateNulls) {
				continue;
			}
			$val = $this->_db->_quoteField( $v, $object->_getFieldType( $k ) );
			$tmp[] = $this->_db->NameQuote( $k ) . '=' . $val;
		}
		return DB::Query( sprintf( $fmtsql, implode( ",", $tmp ) , $where ) );
	}

	/**
	* 将表输出到XML
	* @param boolean Map foreign keys to text values
	*/
	function toXML( $mapKeysToText=false ) {
		$xml = '<record table="' . $this->_tbl . '"';

		if ($mapKeysToText) {
			$xml .= ' mapkeystotext="true"';
		}
		$xml .= '>';
		foreach (get_object_vars( $this ) as $k => $v) {
			if (is_array($v) or is_object($v) or $v === NULL) {
				continue;
			}
			if ($k[0] == '_') { // internal field
				continue;
			}
			$xml .= '<' . $k . '><![CDATA[' . $v . ']]></' . $k . '>';
		}
		$xml .= '</record>';
		return $xml;
	}

	/**
	 * 重新排序
	 *
	 * @access public
	 * @param string Additional where query to limit ordering to a particular subset of records
	 */
	function reorder( $where='' ) {
		$k = $this->_tbl_key;

		if (!in_array( 'displayorder', $this->getPublicProperties()  )) {
			$this->_error = get_class( $this ).' 不支持排序操作。';
			return false;
		}
		$query = 'SELECT '.$this->_tbl_key.', displayorder'
		. ' FROM '. $this->_tbl
		. ' WHERE displayorder >= 0' . ( $where ? ' AND '. $where : '' )
		. ' ORDER BY displayorder,'.$this->_tbl_key.' DESC '
		;
		DB::query($query);
		if(!$orders = DB::fetch_all()){
			$this->_error = $this->_db->error().'<br>没有找到用于排序的记录！';
			return false;
		}
		foreach((array)$orders as $i=>$row){
			if ($row['displayorder'] >= 0) {
				if ($row['displayorder'] != $i+1) {
					$row['displayorder'] = $i+1;
					$query = 'UPDATE '.$this->_tbl
					. ' SET displayorder = '. (int) $row['displayorder']
					. ' WHERE '. $this->_tbl_key .' = '. $this->_db->Quote($row[$this->_tbl_key])
					;
					DB::Query( $query);
				}
			}
		}
		return true;
	}
	function getForm(){
		$keys = $this->getPublicProperties();
		foreach ($keys as $k) {
			$row[$k] = $this->$k;
		}
		$tbname = str_replace(array('#__', $this->_db->tablepre), '', $this->_tbl);
		$sql = "SELECT * FROM #__dbdict WHERE tbname='$tbname' ORDER BY displayorder ASC, id ASC";
		$rows = $this->_db->fetch_all($sql, 'fieldname');
		if (!$rows) {
			$rows = array();
			foreach ($keys as $k) {
				if ($k != 'dateline' && $k != 'lastupdate') {
					if ($k == $this->_tbl_key)
						$input = 'hidden';
					elseif ($k == 'catid')
						$input = 'category';
					elseif ($k == 'enabled')
						$input = 'yesorno';
					else
						$input = 'input';
					$rows[$k] = array(
						 'fieldname' => $k,
						 'title' => $k,
						 'inlist' => 1,
						 'inputmode' => $input,
						 'required' => false,
						 'valid' => ''
					);
					if (in_array($k, $this->_required)) {
						$rows[$k]['required'] = true;
					}
				}
			}
		}
		if (!$rows)
			return false;
		$html = label(array("type" => "form-start", "name" => "theform", "action" => 'admin.php?m=common&a=save&table='.$tbname));
		$html .= label(array("type" => "div-start"));
		$html .= label(array("type" => "table-start"));
		//修复批量上传的BUG
		if (isset($rows['hash']) && !$row['hash']) {
			$row['hash'] = smd5(time() . random(6));
		}
		$script = '';
		//pre($rows);
		foreach ($rows as $v) {
			if (!isset($v['inputmode']))
				$v['inputmode'] = 'input';
			if (!isset($row[$this->_tbl_key])) {
				if (isset($v['defaultvalue']) && $v['defaultvalue'])
					$row[$v['fieldname']] = $v['defaultvalue'];
				else
					$row[$v['fieldname']] = '';
			}
			if (!isset($v['required']))
				$v['required'] = false;
			if (!isset($v['valid']))
				$v['valid'] = '';
			if ($v['required']) {
				$script = '<script>
            $(document).ready( function() {
                $("#theform").validationEngine("attach", {promptPosition : "topLeft", autoPositionUpdate : true});
            });</script>';
			}
			if ($v['inputmode'] == 'editor') {
				$submit = ' onclick="edit_save();"';
			}
			if (!isset($v['note']))
				$v['note'] = '';
			$data = array(
				 "title" => $v['title'],
				 "name" => $v['fieldname'],
				 "type" => $v['inputmode'],
				 "value" => $row[$v['fieldname']],
				 'note' => $v['note'],
				 'required' => $v['required'],
				 'valid' => $v['valid']
			);
			if (($v['inputmode'] == 'select-input' || $v['inputmode'] == 'select' || $v['inputmode'] == 'checkbox' || $v['inputmode'] == 'radio') && $v['defaultvalue']) {
				$data['options'] = processOptions($v['defaultvalue'], $v['inputmode']);
			}
			if ($v['inputmode'] === 'upload') {
				if (isset($row['hash'])) {
					$data['hash'] = $row['hash'];
				}
				$data['allowmax'] = '10';
			}
			$html .=label($data);
		}
		$html .=label(array("type" => 'submit', 'name' => 'submit', 'value' => '提交', 'other' => $submit));
		$html .=label(array("type" => "table-end"));
		$html .=label(array("type" => "div-end"));
		$html .=label(array("type" => "form-end"));
		$html = $script . $html;
		return $html;
	}

}
